// Copyright (c) XiGuan 2023
// Author： xiguan
// Email: xiguan.teng@qq.com
// Create on 2023/10/28
// TODO:
//

#include "Log.h"

#include <sys/stat.h>
#include <sys/time.h>
#include <stdarg.h>

void Log::init(int _level, const char *_path, const char *_suffix,
               int _max_queue_capacity) {
  is_open_ = true;
  level_ = _level;

  if (_max_queue_capacity > 0) {
    is_async_ = true;
    if (!deque_) {
      std::unique_ptr<BlockDeque<std::string>> new_deque(
          new BlockDeque<std::string>);
      deque_ = std::move(new_deque);

      std::unique_ptr<std::thread> new_thread(
          new std::thread(flush_log_thread));
      write_thread_ = std::move(new_thread);
    }
  } else {
    is_async_ = false;
  }

  //  int year = t.tm_year + 1900; // 年份，考虑基准年份为1900
  //  int month = t.tm_mon + 1;    // 月份（0-11），因此需要加1以获取实际月份
  //  int day = t.tm_mday;         // 日期（1-31）
  //  int hour = t.tm_hour;        // 小时（0-23）
  //  int minute = t.tm_min;      // 分钟（0-59）
  //  int second = t.tm_sec;      // 秒数（0-61）

  line_count_ = 0;
  time_t timer = time(nullptr);             // 当前时间
  struct tm *sys_time = localtime(&timer);  // 转换时间为更易阅读的形式
  struct tm t = *sys_time;

  path_ = _path;
  suffix_ = _suffix;

  char file_name[LOG_NAME_LEN] = {0};
  snprintf(file_name, LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s", path_,
           t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_);
  today_ = t.tm_mday;

  // RAII
  {
    std::lock_guard<std::mutex> locker(mtx_);
    buffer_.RetrieveAll();
    if (fp_) {
      flush();
      fclose(fp_);
    }

    fp_ = fopen(file_name, "a");
    if (fp_ == nullptr) {
      mkdir(path_, 0777);
      fp_ = fopen(file_name, "a");
    }

    assert(fp_ != nullptr);
  }
}

void Log::flush_log_thread() { Log::Instance()->async_write_(); }

void Log::flush() {
  if (is_async_) {
    deque_->flush();
  }
  fflush(fp_);
}

Log *Log::Instance() {
  static Log inst;
  return &inst;
}

void Log::async_write_() {
  std::string str = "";
  while (deque_->pop(str)) {
    std::lock_guard<std::mutex> locker(mtx_);
    fputs(str.c_str(), fp_);
  }
}

int Log::getLevel() {
  std::lock_guard<std::mutex> locker(mtx_);
  return level_;
}

void Log::setLevel(int _level) {
  std::lock_guard<std::mutex> locker(mtx_);
  level_ = _level;
}

Log::Log() {
  line_count_ = 0;
  is_async_ = false;
  write_thread_ = nullptr;
  deque_ = nullptr;
  today_ = 0;
  fp_ = nullptr;
}

Log::~Log() {
  if (write_thread_ && write_thread_->joinable()) {
    while (!deque_->empty()) {
      deque_->flush();
    }
    deque_->close();
    write_thread_->join();
  }

  if (fp_) {
    std::lock_guard<std::mutex> locker(mtx_);
    flush();
    fclose(fp_);
  }
}
bool Log::isOpen() const { return is_open_; }

void Log::write(int _level, const char *format, ...) {
  struct timeval now = {0, 0};
  gettimeofday(&now, nullptr);  // 当前时间，精确带秒以及微秒
  time_t t_sec = now.tv_sec;
  struct tm *sys_time = localtime(&t_sec);
  struct tm t = *sys_time;

  va_list vaList;

  /** 日志日期 日志行数 */
  if (today_ != t.tm_mday || (line_count_ && (line_count_ % MAX_LINES == 0))) {
    std::unique_lock<std::mutex> locker(mtx_);
    locker.unlock();

    char new_file[LOG_NAME_LEN];
    char tail[36] = {0};

    snprintf(tail, sizeof(tail), "%04d_%02d_%02d", t.tm_year + 1900,
             t.tm_mon + 1, t.tm_mday);

    if (today_ != t.tm_mday) {
      snprintf(new_file, LOG_NAME_LEN - 72, "%s/%s%s", path_, tail, suffix_);
      today_ = t.tm_mday;
      line_count_ = 0;
    } else {
      snprintf(new_file, LOG_NAME_LEN - 72, "%s/%s-%d%s", path_, tail,
               (line_count_ / MAX_LINES), suffix_);
    }

    locker.lock();
    flush();
    fclose(fp_);
    fp_ = fopen(new_file, "a");
    assert(fp_ != nullptr);
  }

  {
    std::unique_lock<std::mutex> locker(mtx_);
    line_count_++;

    int n =
        snprintf(buffer_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld",
                 t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min,
                 t.tm_sec, now.tv_usec);

    buffer_.HasWritten(n);
    append_log_level_title(level_);

    va_start(vaList, format);
    int m = vsnprintf(buffer_.BeginWrite(), buffer_.WritableBytes(), format, vaList);
    va_end(vaList);

    buffer_.HasWritten(m);
    buffer_.Append("\n\0", 2);

    if (is_async_ && deque_ && !deque_->full()) {
      deque_->push_back(buffer_.RetrieveAllToStr());
    } else {
      fputs(buffer_.Peek(), fp_);
    }
    buffer_.RetrieveAll();
  }
}

void Log::append_log_level_title(int _level) {
  switch (_level) {
    case 0:
      buffer_.Append("[debug]", 9);
      break;
    case 1:
      buffer_.Append("[info]", 9);
      break;
    case 2:
      buffer_.Append("[warn]", 9);
      break;
    case 3:
      buffer_.Append("[error]", 9);
      break;
    default:
      buffer_.Append("[info]", 9);
      break;
  }
}
